package cate.game.role.res.furniturecall;


import cate.common.table.d.*;
import cate.common.table.item.furniture.row.FurnitureItemBaseRow;
import cate.common.table.item.hero.StarGroupList;
import cate.common.table.res.furniturecall.row.*;
import cate.common.util.GameResult;
import cate.game.chat.ChatContentParam;
import cate.game.chat.ChatFacade;
import cate.game.client.msg.MergeMsgSender;
import cate.game.res.MixRes;
import cate.game.res.MixResAdder;
import cate.game.res.MixResItem;
import cate.game.res.furniturecall.FurnitureCallLog;
import cate.game.role.FuncOpenChecker;
import cate.game.role.Role;
import cate.game.role.bag.furniture.Furniture;
import cate.game.role.res.furniturecall.msg.FurnitureCallDataResp;
import cate.game.role.res.furniturecall.msg.FurnitureCallResp;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import easy.java.dev.note.NoteClass;
import easy.java.dev.note.NoteField;
import easy.java.practice.errorcode.EcResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.annotation.Transient;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

@NoteClass(value = "手办抽奖相关数据")
public class FurnitureCall extends FuncOpenChecker {
    @NoteField(value = "Map<召唤类型, 召唤状态>")
    public Map<Integer, CallTypeStatus> statusMap;

    @NoteField(value = "Map<召唤模式, 召唤状态>")
    @JsonIgnore
    public Map<Integer, CallModeStatus> callModeMap;

    @NoteField(value = "召唤积分")
    public int points;

    @NoteField(value = "已经领取的奖励tid")
    public List<Integer> hasGain;

    @JsonIgnore
    @NoteField(value = "最近更新的木桶excel版本")
    public String version;

    @NoteField("个人召唤记录")
    @JsonIgnore
    public List<Integer> selfLogs;

    @NoteField("今日碎钻召唤次数")
    public int m1CallTimes;

    @Transient
    @JsonProperty
    public List<FurnitureCallLog> getLogs(){
        final List<FurnitureCallLog> logs = role.getGame().res.furnitureCall.getLogs();
        return logs.size() > 1 ? logs.subList(0,2) : logs;
    }

    @JsonProperty("callNiubiTimes")
    public int getCallNiubiTimes() {
        return getCallMode(1).callNiuBiTimes;
    }

    @Override
    public void initialize(Role role) {
        super.initialize(role);
        if (statusMap == null) {
            statusMap = new HashMap<>();
        }
        if (callModeMap == null) {
            callModeMap = new HashMap<>();
        }
        if (hasGain == null) {
            hasGain = new ArrayList<>();
        }
        if (selfLogs == null) {
            selfLogs = new ArrayList<>();
        }
    }

    /**
     * 抽奖操作
     *
     * @return r
     */
    public EcResult<Void> handleCall(int type) {
        EcResult<Void> r = new EcResult<>();
        FurnitureCallTypeRow row = role.getGame().table.furnitureCall.type.get(type);
        if (row == null) {
            return r.fail("手办召唤类型有误");
        }
        // 前置检测 （召唤条件判断 背包格子检测 资源检测并扣除）
        r = preCheck(row);
        if (!r.ok()) {
            return r;
        }
        // 执行召唤操作（把手办塞进背包）
        callExe(row);
        return r;
    }

    /**
     * 召唤前置检测（召唤条件判断 背包格子检测 资源检测并扣除）
     *
     * @param row row
     * @return r
     */
    private EcResult<Void> preCheck(FurnitureCallTypeRow row) {
        EcResult<Void> r = new EcResult<>();
        if (role.getBag().furniture.gridEmpty() < row.rcount) {
            return r.fail("手办背包空间不足");
        }
        // 召唤状态
        CallTypeStatus status = getCallTypeStatus(row.id);
        // 消耗资源优先级：1、一次 2、十次 3、xxx后续开放预留
        // 免费次数检测
        if (status.freeTimes >= row.rcount) {
            status.freeTimes -= row.rcount;
            return r.success();
        }

        // 手办币检测
        if (StringUtils.isNotBlank(row.costStr)) {
            MixRes consume = new MixRes(row.costStr);
            GameResult<MixResItem> consumeRes = consume.consume(role, null);
            if (!consumeRes.ok()) {
                if (m1CallTimes + row.rcount <= GDFurniture.M1_CALL_TIMES) {
                    consumeRes = new MixRes(row.costM1Str).consume(role);
                    if (consumeRes.ok()) {
                        m1CallTimes += row.rcount;
                        new MixResAdder().setRes(row.rewardStr).exe(role);
                    }
                }
                if (!consumeRes.ok()) {
                    role.getGame().notice.tipResItem(role, consumeRes.data);
                    return r.fail(consumeRes.message);
                }
            }
            role.getHistory().action.furnitureCallItemConsume(row.rcount);
            return r;
        }
        return r.fail("手办抽奖失败");
    }

    private void callExe(FurnitureCallTypeRow row) {
        // 召唤模式状态
        CallModeStatus callModeStatus = getCallMode(row.mode);
        List<Integer> records = new ArrayList<>();
        MixRes reward = new MixRes();
        for (int i = 1; i <= row.rcount; i++) {
            int rewardId = deal(callModeStatus, row, records);
            if (rewardId > 0) {
                records.add(rewardId);
            }
            callModeStatus.callTimes++;
            addSelfLogs(rewardId);

            MixResItem item = getRewardItem(rewardId);
            if (isFurniture(item)) {
                if (isZuiNiubiFurniture(item)) {
                    callModeStatus.callNiuBiTimes = 0;
                    role.getGame().res.furnitureCall.addLog(role.getBase().name, rewardId);
                    lampZuiNiubi(item);
                } else if (isNiubiFurniture(item)){
                    callModeStatus.callNiuBiTimes ++;
                    callModeStatus.callStarTimes = 0;
                    role.getGame().res.furnitureCall.addLog(role.getBase().name, rewardId);
                } else {
                    callModeStatus.callNiuBiTimes ++;
                    callModeStatus.callStarTimes ++;
                }
                Furniture furniture = new Furniture(role, item.tid);
                furniture.initialize(role);
                reward.items.add(new MixResItem(furniture));
            } else {
                callModeStatus.callStarTimes ++;
                callModeStatus.callNiuBiTimes ++;
                reward.items.add(item);
            }
        }
        MergeMsgSender sender = new MergeMsgSender();
        reward.add(role, true, 0, sender);
        sender.send(role, true);
        role.getHistory().action.furnitureCallTime(row.rcount);
        points += row.cangetJf;
        logger.info("玩家{}的手办召唤结果为{}", role.getBase().name, records);
        role.sendNow(new FurnitureCallResp(row.id, reward));
        noticeUpdate();
    }

    private void lampZuiNiubi(MixResItem item) {
        List<ChatContentParam> params = new ArrayList<>();
        params.add(ChatContentParam.c(ChatContentParam.TYPE_STRING, role.getBase().name));
        params.add(ChatContentParam.c(ChatContentParam.TYPE_MIX_RES, new MixRes().addItem(item)));
        role.getGame().notice.lampOnline(role, GDNotice.TextId.FURNITURE_NIUBI, params);
    }

    public void addSelfLogs(int rewardId){
        selfLogs.add(0, rewardId);
        if (selfLogs.size() > GDSpiceCall.LOG_MAX) {
            selfLogs.remove(selfLogs.size() - 1);
        }
    }

    private int deal(CallModeStatus callModeStatus, FurnitureCallTypeRow row, List<Integer> records){
        int rewardId = cheatCall(callModeStatus);
        FurnitureCallGroupsRow groupTpl;
        if (rewardId > -1) {
            //进作弊池
            return specialDeal(row, rewardId, records, true);
        }
        StarGroupList.Item item = row.config.get();
        if (item == null) {
            return 0;
        }
        groupTpl = role.getGame().table.furnitureCall.group.randomHero(item.groupId);
        rewardId = groupTpl.id;
        return specialDeal(row, rewardId, records, false);
    }

    private boolean isNiubiFurniture(int rewardId) {
        MixResItem rewardItem = getRewardItem(rewardId);
        if (!isFurniture(rewardItem)) {
            return false;
        }
        return isNiubiFurniture(rewardItem);
    }

    private boolean isNiubiFurniture(MixResItem rewardItem) {
        FurnitureItemBaseRow furnitureBaseRow = role.getGame().table.furniture.base.get(rewardItem.tid);
        if (furnitureBaseRow == null) {
            return false;
        }
        return furnitureBaseRow.quality == GDProp.Quality.Q4;
    }

    private boolean isZuiNiubiFurniture(MixResItem rewardItem) {
        FurnitureItemBaseRow furnitureBaseRow = role.getGame().table.furniture.base.get(rewardItem.tid);
        if (furnitureBaseRow == null) {
            return false;
        }
        return furnitureBaseRow.quality >= GDProp.Quality.Q5;
    }

    private boolean isFurniture(MixResItem rewardItem) {
        return rewardItem != null && rewardItem.type == GDObj.Type.FURNITURE;
    }

    private MixResItem getRewardItem(int rewardId) {
        FurnitureCallGroupsRow rewardRow = role.getGame().table.furnitureCall.group.get(rewardId);
        return MixResItem.parse(rewardRow.rewardStr);
    }

    private int specialDeal(FurnitureCallTypeRow row, int rewardId, List<Integer> records, boolean replaceRecords){
        boolean niubi = isNiubiFurniture(rewardId);
        long niubiNum = records.stream().filter(this::isNiubiFurniture).count();
        if (!niubi || niubiNum < row.countMax) {
            return rewardId;
        }
        //防脸白
        StarGroupList.Item item = row.replaceConfig.get();
        if (item == null) {
            return rewardId;
        }
        int replaceRewardId = rewardId;
        FurnitureCallGroupsRow groupsRow = role.getGame().table.furnitureCall.group.randomHero(item.groupId);
        if (groupsRow != null) {
            replaceRewardId = groupsRow.id;
        }
        if (replaceRecords) {
            int pos = -1;
            for (int i = records.size() - 1; i > 0; i--) {
                if (isNiubiFurniture(records.get(i))) {
                    pos = i;
                    break;
                }
            }
            if (pos > -1) {
                records.remove(pos);
                records.add(replaceRewardId);
                return rewardId;
            }
        }
        return replaceRewardId;
    }

    // 抽奖作弊（抽奖总次数到一定数量，进入特定池子抽奖）
    private int cheatCall(CallModeStatus mod) {
        // 根据召唤模式和召唤次数判断是否需要作弊
        FurnitureCallCheatRow row = role.getGame().table.furnitureCall.cheat.getRow(mod.callMode, mod.callTimes + 1);
        StarGroupList.Item item = null;
        if (row != null) {
            item = row.config.get();
        }
        // 保底作弊
        if (item == null) {
            FurnitureGuranteeRow guranteeRow = role.getGame().table.furnitureCall.gurantee.getQ4Row(mod.callStarTimes + 1);
            if (guranteeRow != null) {
                item = guranteeRow.config.get();
            }
        }
        // 红保底
        if (item == null) {
            FurnitureGuranteeRow niubiGuranteeRow = role.getGame().table.furnitureCall.gurantee.getQ5Row(mod.callNiuBiTimes + 1);
            if (niubiGuranteeRow != null) {
                item = niubiGuranteeRow.config.get();
            }
        }
        if (item != null) {
            FurnitureCallGroupsRow groupRow = role.getGame().table.furnitureCall.group.randomHero(item.groupId);
            if (groupRow != null) {
                return groupRow.id;
            }
        }
        // 说明不需要作弊
        return -1;
    }

    public EcResult<Void> gainGift(int tid) {
        EcResult<Void> r = new EcResult<>();
        FurnitureGainRow gainRow = role.getGame().table.furnitureCall.gain.get(tid);
        if (gainRow == null) {
            return r.fail("错误的宝箱ID");
        }
        if (points < gainRow.cnt) {
            return r.fail("召唤次数不足");
        }
        if (hasGain.contains(tid)) {
            return r.fail("已经领取过该奖励！");
        }
        hasGain.add(tid);
        MixRes reward = new MixRes(gainRow.awards);
        reward.add(role, true, GDOperation.FURNITURE_RWD);
        role.getGame().notice.dialogReward(role, reward);
        if (hasGain.size() >= role.getGame().table.furnitureCall.gain.getList().size()) {
            hasGain.clear();
            int tmp = points - role.getGame().table.furnitureCall.gain.getMaxCnt();
            points = Math.max(tmp, 0);
        }
        noticeUpdate();
        return r;
    }

    @JsonIgnore
    public CallTypeStatus getCallTypeStatus(int type) {
        return statusMap.computeIfAbsent(type, id1 -> {
            CallTypeStatus status = new CallTypeStatus();
            status.type = type;
            return status;
        });
    }

    @JsonIgnore
    public CallModeStatus getCallMode(int callMode) {
        return callModeMap.computeIfAbsent(callMode, callMode1 -> {
            CallModeStatus status = new CallModeStatus();
            status.callMode = callMode;
            return status;
        });
    }

    public void noticeUpdate() {
        role.sendNow(new FurnitureCallDataResp(this));
    }

    @Override
    public int getFuncId() {
        return GDFunc.FURNITURE_CALL;
    }

    @Override
    public void onAfterOpen() {
        onDaySpan(false);
    }

    @Override
    public void onDaySpan(boolean silence) {
        if (isOpen()) {
            for (FurnitureCallTypeRow tpl : role.getGame().table.furnitureCall.type.list) {
                CallTypeStatus status = getCallTypeStatus(tpl.id);
                if(tpl.freeMax == 0) {
                    status.freeTimes = 0;
                }else if(status.freeTimes < tpl.freeMax){
                    status.freeTimes += tpl.dayCntAdd;
                }
            }
        }
        m1CallTimes = 0;
        if (!silence) {
            noticeUpdate();
        }
    }


    @NoteClass(value = "召唤类型状态")
    private static class CallTypeStatus {
        @NoteField(value = "召唤类型")
        public int type;

        @NoteField(value = "上次免费刷新时间")
        public long lastRefreshTime;

        @NoteField(value = "剩余免费次数")
        public int freeTimes;
    }

    @NoteClass(value = "召唤模式状态")
    private static class CallModeStatus {
        @NoteField(value = "召唤模式", detail = "0普通召唤 1积分召唤")
        public int callMode;

        @NoteField(value = "累积召唤次数")
        public int callTimes;

        @NoteField(value = "累计未中五星召唤次数")
        public int callStarTimes;

        @NoteField(value = "累计未中牛逼次数")
        @JsonIgnore
        public int callNiuBiTimes;
    }

    public void gmCall(int type, int times){
        FurnitureCallTypeRow row = role.getGame().table.furnitureCall.type.get(type);
        if(row == null){
            role.getGame().notice.message(role, "错误的召唤类型");
            return;
        }
        Map<Integer, Integer> reward = new HashMap<>();
        Map<Byte, Integer> qualityNum = new HashMap<>();
        CallModeStatus callModeStatus = getCallMode(1);
        for (int i = 1; i <= times; i++) {
            List<Integer> records = new ArrayList<>();
            for (int j = 1; j <= row.rcount; j++) {
                int rewardId = deal(callModeStatus, row, records);
                if (rewardId > 0) {
                    records.add(rewardId);
                }
                callModeStatus.callTimes++;
                MixResItem item = getRewardItem(rewardId);
                byte quality = -1;
                if (isFurniture(item)) {
                    try{
                        quality = role.getGame().table.furniture.base.get(item.tid).quality;
                    }catch(Exception e) {
                        logger.info("错误的道具{}", item);
                    }

                    if (isNiubiFurniture(item)) {
                        callModeStatus.callStarTimes = 0;
                    } else {
                        callModeStatus.callStarTimes ++;
                    }
                } else {
                    callModeStatus.callStarTimes ++;
                }
                reward.put(rewardId, reward.getOrDefault(rewardId, 0) + 1);
                qualityNum.put(quality, qualityNum.getOrDefault(quality, 0) + 1);
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append("时间：").append(LocalDateTime.now(ZoneId.systemDefault())).append("\n\n");
        int idx = 0;
        sb.append("模拟结果:\n");
        for (Map.Entry<Integer, Integer> id : reward.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getKey)).collect(Collectors.toList())) {
            sb.append(id.getKey()).append(":").append(id.getValue()).append(", ");
            if(++idx % 3 == 0){
                sb.append("\n");
            }
        }
        sb.append("\n");
        sb.append("总计: ");
        sb.append("\n");
        for(Byte q : qualityNum.keySet()) {
            switch (q){
                case -1:
                    sb.append("垃圾道具");
                    break;
                case (byte) 3:
                    sb.append("紫色家具 ");
                    break;
                case (byte) 4:
                    sb.append("黄色家具 ");
                    break;
                case (byte) 5:
                case (byte) 6:
                    sb.append("红色家具 ");
                    break;
                default:
                    break;
            }
            sb.append(qualityNum.get(q)).append("次").append("\n");
        }
        logger.info(sb.toString());
        ChatFacade.sendPublicFromClient(role, GDChat.Channel.WORLD, 0, sb.toString(), new ArrayList<>());
    }
}
